﻿using AutoCommunication.Base;
using AutoCommunication.Enums;
using AutoCommunication.Helper;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Net;
using System.Net.Sockets;

namespace AutoCommunication.Clients
{
    public class DevModbusTcp : BaseSocketDevice<VarableModbus>
    {
        public DevModbusTcp()
        {
         
        }

        #region 属性

        [JsonProperty("设备名")]
        [DisplayName("设备名"), Category("设备信息")]
        public override string Name { get; set; } = "ModbusTcp";

        [JsonProperty("字节格式"), DisplayName("字节格式"), Category("设备信息")]
        public override EndianFormat Format { get; set; } = EndianFormat.DCBA;

        /// <summary>
        /// IP地址
        /// </summary>
        [JsonProperty("IP地址"), DisplayName("IP地址"), Category("设备信息")]
        public override string IpAddress { get; set; } = "127.0.0.1";

        /// <summary>
        /// IP地址
        /// </summary>
        [JsonProperty("端口"), DisplayName("端口号"), Category("设备信息")]
        public override int Port { get; set; } = 502;

        #endregion 属性

        #region 复写的方法

        [Obsolete]
        protected override Result Connect()
        {
            var result = new Result();
            socket?.SafeClose();
            socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            try
            {
                //超时时间设置
                socket.ReceiveTimeout = Timeout;
                socket.SendTimeout = Timeout;

                //连接
                socket.Connect(IpEndPoint);
            }
            catch (Exception ex)
            {
                socket?.SafeClose();
                result.IsSucceed = false;
                result.Err = ex.Message;
                result.ErrCode = 408;
                result.Exception = ex;
            }
            return result.EndTime();
        }

        protected override byte[] GetWriteCommand(Group<VarableModbus> group)
        {
            var info = group.Varablelist[0];
            byte[] buffer = null;
            ushort startaddress;
            if (plcAddresses)
            {
                startaddress = Convert.ToUInt16(((ushort)group.StartByteAddress) % 10000 - 1);
            }
            else
            {
                startaddress = (ushort)group.StartAddress;
            }
            switch (info.ModbusStore)
            {
                case ModbusStore.InCoil:
                case ModbusStore.InRegister:
                    throw new Exception("不能向输入线圈|输入寄存器写数据哦");
                case ModbusStore.OutCoil:
                    if (group.Varablelist.Count == 1) //写单个线圈
                    {
                        buffer = new byte[12];
                        buffer[0] = 0x00;
                        buffer[1] = 0x00; //进阶:检验信息，用来验证response是否串数据了;设置什么,返回就是什么,可以通过这个,确认返回的报文是不是我们发送对应的
                        buffer[2] = 0x00;
                        buffer[3] = 0x00;//固定,表示modbus协议

                        buffer[4] = 0x00;
                        buffer[5] = 0x06;//表示的是 该字节 以后 的字节个数

                        buffer[6] = info.StationNumber; //站号
                        buffer[7] = (byte)ModbusFunCode.WriteSingleCoil;  //功能码
                        buffer[8] = BitConverter.GetBytes(startaddress)[1];
                        buffer[9] = BitConverter.GetBytes(startaddress)[0];//寄存器地址

                        var value = Convert.ToBoolean(group.Varablelist.First().WriteValue);
                        buffer[10] = (byte)(value ? 0xFF : 0x00);//固定,只能是FF或者00
                        buffer[11] = 0x00;//固定00
                        return buffer;
                    }
                    else if (group.Varablelist.Count >= 2)//写多个线圈
                    {
                        var mod = group.Varablelist.Count % 8 == 0;//是否是8的倍数
                        var count = (byte)(mod ? group.Varablelist.Count / 8 : group.Varablelist.Count / 8 + 1);

                        buffer = new byte[13 + count];//本次报文一共有多少个字节:固定13个+我们的Values值

                        buffer[0] = 0x00;
                        buffer[1] = 0x00; //进阶:检验信息，用来验证response是否串数据了

                        buffer[2] = 0x00;
                        buffer[3] = 0x00;//固定,表示modbus协议

                        buffer[4] = (byte)(0);
                        buffer[5] = (byte)(7 + count);//表示后面一共多少个字节:固定7个+我们的Values值

                        buffer[6] = info.StationNumber;//站号

                        buffer[7] = (byte)ModbusFunCode.WriteMutilCoil;//功能码

                        buffer[8] = BitConverter.GetBytes(startaddress)[1];
                        buffer[9] = BitConverter.GetBytes(startaddress)[0];//寄存器起始地址

                        buffer[10] = (byte)(group.Varablelist.Count / 256);
                        buffer[11] = (byte)(group.Varablelist.Count % 256);//写的线圈的个数:前面只是定义了多少个字节,没有定义到底是多少个线圈,不被8整除的时候,后面的字节有些是无效的,这个参数很重要

                        buffer[12] = count;//之后的 字节 个数:计算公式为我们要写的线圈个数/8
                        byte[] vals = new byte[count];
                        //赋值程序,可以考虑把他提取到外面,想了下,通常写入不会很多,所以这里也没事
                        for (int i = 0; i < group.Varablelist.Count; i++)
                        {
                            vals[i / 8] = vals[i / 8].SetBit(i % 8, Convert.ToBoolean(group.Varablelist[i].WriteValue));
                        }
                        vals.CopyTo(buffer, 13);
                        return buffer;
                    }
                    break;

                case ModbusStore.HoldRegister:
                    if (group.Varablelist.Count == 1 && (group.Varablelist.First().DataType == DataTypeEnum.Int16 || group.Varablelist.First().DataType == DataTypeEnum.UInt16))//写单个寄存器
                    {
                        buffer = new byte[12];
                        buffer[0] = 0x00;
                        buffer[1] = 0x00; //进阶:检验信息，用来验证response是否串数据了

                        buffer[2] = 0x00;
                        buffer[3] = 0x00;//固定,表示modbus协议

                        buffer[4] = 0x00; ;
                        buffer[5] = 6;//表示的是header handle后面还有多长的字节

                        buffer[6] = info.StationNumber;//站号
                        buffer[7] = (byte)ModbusFunCode.WriteSingleRegister;//功能码

                        buffer[8] = BitConverter.GetBytes(startaddress)[1];
                        buffer[9] = BitConverter.GetBytes(startaddress)[0];//寄存器起始地址
                        //将值转化为字节;这里逻辑可能复杂了,有好的逻辑,可以告诉我
                        var vals = Convert.ToDouble(group.Varablelist[0].WriteValue).ToBytes(group.Varablelist.First().DataType, Format);
                        //这里需要注意顺序,bitconvert函数默认转化为小端模式,所以Format选择了DCBA
                        buffer[10] = vals[0];
                        buffer[11] = vals[1];
                        return buffer;
                    }
                    else
                    {
                        var count = 0;
                        foreach (var item in group.Varablelist)
                        {
                            count = count + Common.GetBytelength(item) / 2;//获取占用寄存器
                        }
                        var vals = new byte[count * 2];//需要写入的字节数组
                        int i = 0;
                        foreach (var item in group.Varablelist)//为需要写入的字节数组赋值
                        {
                            var val = Convert.ToDouble(item.WriteValue).ToBytes(item.DataType, Format);
                            val.CopyTo(vals, i);
                            i = i + Common.GetBytelength(item);
                        }

                        buffer = new byte[13 + count * 2];//固定长度为13+需要写入的字节数组长度
                        buffer[0] = 0x00; //
                        buffer[1] = 0x00; //进阶:检验信息，用来验证response是否串数据了
                        buffer[2] = 0x00;
                        buffer[3] = 0x00;//固定表示modbus协议

                        buffer[4] = (byte)(0);
                        buffer[5] = (byte)(7 + count * 2);//表示后面一共多少个字节

                        buffer[6] = info.StationNumber; //站号
                        buffer[7] = (byte)ModbusFunCode.WriteMutilRegister;  //功能码

                        buffer[8] = BitConverter.GetBytes(startaddress)[1];
                        buffer[9] = BitConverter.GetBytes(startaddress)[0];//寄存器地址

                        buffer[10] = (byte)(count / 256);
                        buffer[11] = (byte)(count % 256);//写寄存器数量

                        buffer[12] = (byte)(count * 2);//表示后面一共有多少个字节

                        vals.CopyTo(buffer, 13);//赋值
                    }
                    break;
            }
            return buffer;
        }

        protected override List<byte[]> GetReadCommand(Group<VarableModbus> group)
        {
            List<byte[]> result = new();
            var info = group.Varablelist[0];
            byte[] buffer = new byte[12];
            buffer[0] = 0x00;
            buffer[1] = 0x00;//进阶设定:可以不为0,返回时候验证这个头部,用来防治报文传输混乱

            buffer[2] = 0x00;
            buffer[3] = 0x00;//表示tcp/ip 的协议的Modbus的协议 固定

            buffer[4] = 0x00;
            buffer[5] = 0x06;//表示的是该字节以后的字节长度

            buffer[6] = info.StationNumber;  //站号
            switch (info.ModbusStore)//区域不同,功能码不同
            {
                case ModbusStore.InCoil:
                    buffer[7] = 0x02;   //功能码
                    break;

                case ModbusStore.OutCoil:
                    buffer[7] = 0x01;   //功能码
                    break;

                case ModbusStore.InRegister:
                    buffer[7] = 0x04;   //功能码
                    break;

                case ModbusStore.HoldRegister:
                    buffer[7] = 0x03;   //功能码
                    break;
            }
            ushort startaddress; //起始地址
            //todo 没有用过plc地址通讯过,但是poll等测试软件都有这个功能,谁有资料麻烦分享(以下转化是抄的)
            if (plcAddresses)
            {
                startaddress = Convert.ToUInt16(((ushort)group.StartByteAddress) % 10000 - 1);
            }
            else
            {
                startaddress = (ushort)group.StartAddress;
            }
            buffer[8] = BitConverter.GetBytes(startaddress)[1];
            buffer[9] = BitConverter.GetBytes(startaddress)[0];//读取的起始地址(寄存器和线圈都一样)

            buffer[10] = BitConverter.GetBytes((ushort)group.Length)[1];//要读取的长度:多少个寄存器?多少位?
            buffer[11] = BitConverter.GetBytes((ushort)group.Length)[0];
            result.Add(buffer);
            return result;
        }

        protected override Result<byte[]> SendAndRevice(byte[] command)
        {
            lock (Readlock)
            {
                Result<byte[]> result = new Result<byte[]>();
                try
                {
                    socket.Send(command);
                    var socketReadResul = socket.Read(8);
                    if (!socketReadResul.IsSucceed)
                        return socketReadResul;
                    var headPackage = socketReadResul.Value;
                    int length = headPackage[4] * 256 + headPackage[5] - 2;
                    socketReadResul = socket.Read(length);
                    if (!socketReadResul.IsSucceed)
                        return socketReadResul;
                    var dataPackage = socketReadResul.Value;
                    result.Value = headPackage.Concat(dataPackage).ToArray();
                    result.ResponseData = new byte[result.Value.Length - 9];
                    Array.Copy(result.Value, 9, result.ResponseData, 0, result.ResponseData.Length);
                    return result.EndTime();
                }
                catch (Exception ex)
                {
                    result.IsSucceed = false;
                    result.Err = ex.Message;
                    result.AddErr2List();
                    return result.EndTime();
                }
            }
        }

        protected override Result CheckOther()
        {
            Result result = new Result();
            if (IPAddress.TryParse(IpAddress, out IPAddress ip))
            {
                IpEndPoint = new IPEndPoint(ip, Port);
            }
            else
            {
                result.IsSucceed = false;
                result.Err = "IP地址格式错误";
            }
            return result;
        }

        protected override Result CheckDeviceParameters()
        {
            return new Result();
        }//Todo

        protected override List<Group<VarableModbus>> Group(List<VarableModbus> AllVarable)
        {
            Result result = new Result();
            List<Group<VarableModbus>> GroupList = new List<Group<VarableModbus>>();
            var g1 = AllVarable.GroupBy(x => x.StationNumber);//根据站号分组
            foreach (var group in g1)
            {
                var g2 = group.GroupBy(x => x.ModbusStore);//在根据存储区分组
                IOrderedEnumerable<VarableModbus> group2order = null;
                foreach (var group2 in g2)//相同站号下面不同的存储区
                {
                    Group<VarableModbus> gp = new Group<VarableModbus>();
                    switch (group2.Key)
                    {
                        case ModbusStore.InCoil:
                        case ModbusStore.OutCoil:
                            group2order = group2.OrderBy(x => x.StartByteAddress).ThenBy(x => x.BitOffset);//根据开始地址排序
                            gp.StartByteAddress = group2order.First().StartByteAddress;//将作为这个组的起始地址
                            gp.StartAddress = group2order.First().StartByteAddress * 8 + group2order.First().BitOffset;
                            gp.Length = 1;
                            GroupList.Add(gp);
                            foreach (var item in group2order)
                            {
                                //
                                if (item.StartByteAddress * 8 + item.BitOffset - GroupList.Last().StartAddress < MaxReadBits)////要测试
                                {
                                    GroupList.Last().Varablelist.Add(item);
                                    GroupList.Last().Length = (item.StartByteAddress * 8 + item.BitOffset - GroupList.Last().StartAddress) + 1;
                                }
                                else
                                {
                                    GroupList.Add(new Group<VarableModbus>()
                                    {
                                        StartByteAddress = item.StartByteAddress,
                                        Length = 1
                                    }); ;//超过大小,另外起一组,其中这个变量的起始地址就是这个组的起始地址
                                    GroupList.Last().Varablelist.Add(item);
                                }
                            }
                            break;

                        case ModbusStore.InRegister:
                        case ModbusStore.HoldRegister:
                            group2order = group2.OrderBy(x => x.StartByteAddress);//根据开始地址排序
                            gp.StartByteAddress = group2order.First().StartByteAddress;//
                            gp.StartAddress = group2order.First().StartByteAddress / 2;
                            var bytelength = Common.GetBytelength(group2order.First());
                            gp.Length = bytelength == 1 ? 1 : bytelength / 2;
                            GroupList.Add(gp);
                            foreach (var item in group2order)
                            {
                                //todo 精通后回来优化这里/已经优化4次
                                if (item.StartByteAddress - GroupList.Last().StartByteAddress < MaxReadBytes)
                                {
                                    //最多125?124?123个寄存器,保险起见,我用123个,也就是123个寄存器;也就是246个字节一次是,因为返回的报文中,第三个字节表示这次报文一共读取到了多少个,所以最多就2^8=256个字节;128个寄存器,返回的报文 站号1,功能码1,字节总数1,数据n.校验2
                                    var bytelength2 = Common.GetBytelength(item) == 1 ? 1 : Common.GetBytelength(item) / 2;
                                    GroupList.Last().Length = item.StartByteAddress / 2 - GroupList.Last().StartByteAddress / 2 + bytelength2;
                                    GroupList.Last().Varablelist.Add(item);
                                }
                                else
                                {
                                    GroupList.Add(new Group<VarableModbus>()
                                    {
                                        StartByteAddress = item.StartByteAddress,
                                        StartAddress = item.StartByteAddress / 2,
                                        Length = Common.GetBytelength(item) == 1 ? 1 : Common.GetBytelength(item) / 2
                                    }); ;//超过大小,另外起一组,其中这个变量的起始地址就是这个组的起始地址
                                    GroupList.Last().Varablelist.Add(item);
                                }
                            }
                            break;
                    }
                }
            }
            return GroupList;
        }

        protected override Result AutoReadGroup()
        {
            Result result = new Result();
            return result;
        }

        #endregion 复写的方法
    }
}